﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using Odyssey.Utils;
using System.Linq;
using Microsoft.Phone.Shell;
using System.Windows.Threading;
using System.Collections.Generic;
using System.ComponentModel;

namespace Odyssey.Controls
{


    /// <summary>
    /// Used to show visual elements on top of the current PhoneApplicationPage.
    /// Overlay automatically set's IsOpen to false when the back button is pressed.
    /// </summary>
    [TemplateVisualState(Name = openedState, GroupName = commonState)]
    [TemplateVisualState(Name = closedState, GroupName = commonState)]
    public class Overlay : ContentControl
    {
        public const string openedState = "Opened";
        public const string closedState = "Closed";
        public const string commonState = "CommonStates";


        public Overlay(FrameworkElement nestedElement)
            : base()
        {
            this.nestedElement = nestedElement;
            DefaultStyleKey = typeof(Overlay);
        }

        internal FrameworkElement nestedElement;
        private Grid panel;
        private Popup popup;
        private PhoneApplicationPage page;
        private Storyboard appBarStoryboard;
        private IApplicationBar originalBar;

        public PhoneApplicationPage Page { get { return page; } }

        /// <summary>
        /// Gets the OverlayMode which is applied.
        /// </summary>
        protected OverlayMode CurrentMode { get; private set; }


        /// <summary>
        /// Specifies how the Overlay is presented.
        /// </summary>
        public OverlayMode Mode
        {
            get { return (OverlayMode)GetValue(ModeProperty); }
            set { SetValue(ModeProperty, value); }
        }

        public static readonly DependencyProperty ModeProperty =
            DependencyProperty.Register("Mode", typeof(OverlayMode), typeof(Overlay), new PropertyMetadata(OverlayMode.Auto));



        /// <summary>
        /// Gets or sets the delay to wait when IsOpen is set to false after to close the popup.
        /// </summary>
        public TimeSpan CloseDelay
        {
            get { return (TimeSpan)GetValue(CloseDelayProperty); }
            set { SetValue(CloseDelayProperty, value); }
        }

        public static readonly DependencyProperty CloseDelayProperty =
            DependencyProperty.Register("CloseDelay", typeof(TimeSpan), typeof(Overlay), new PropertyMetadata(TimeSpan.FromMilliseconds(0.0)));


        /// <summary>
        /// Gets or sets the application bar to show while opened.
        /// </summary>
        public IApplicationBar ApplicationBar
        {
            get { return (IApplicationBar)GetValue(ApplicationBarProperty); }
            set { SetValue(ApplicationBarProperty, value); }
        }

        public static readonly DependencyProperty ApplicationBarProperty =
            DependencyProperty.Register("ApplicationBar", typeof(IApplicationBar), typeof(Overlay), new PropertyMetadata(null, OnApplicationBarChanged));


        private static void OnApplicationBarChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
        {
        }

        /// <summary>
        /// Gets or sets whether the overlay is opened.
        /// </summary>
        public bool IsOpen
        {
            get { return (bool)GetValue(IsOpenProperty); }
            set { SetValue(IsOpenProperty, value); }
        }

        public static readonly DependencyProperty IsOpenProperty =
            DependencyProperty.Register("IsOpen", typeof(bool), typeof(Overlay), new PropertyMetadata(false, OnOpenedChanged));

        private static void OnOpenedChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
        {
            bool newValue = (bool)args.NewValue;

            (o as Overlay).OnOpenedChanged(!newValue, newValue);
        }

        protected virtual void OnOpenedChanged(bool oldValue, bool newValue)
        {
            if (newValue)
            {
                VisualStateManager.GoToState(this, newValue ? openedState : closedState, true);
            };
            if (newValue) OnOpening(); else OnClosing();
        }






        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            InitStateManager();
        }


        private readonly Guid Guid = Guid.NewGuid();


        private void InitStateManager()
        {
            var child = this.GetVisualDescendants().First() as FrameworkElement;
            var groups = VisualStateManager.GetVisualStateGroups(child);
            VisualStateGroup commonGroup = groups.Cast<VisualStateGroup>().FirstOrDefault(g => g.Name == commonState);

            if (commonGroup != null)
            {
                VisualState closedState = commonGroup.States.Cast<VisualState>().FirstOrDefault(s => s.Name == Overlay.closedState);
                if (closedState != null && closedState.Storyboard != null)
                {
                    closedState.Storyboard.Completed += new EventHandler(HandleCloseStoryboardCompleted);
                }
                else
                {
                    commonGroup.CurrentStateChanged += new EventHandler<VisualStateChangedEventArgs>(HandleClosedCommonStateChanged);
                }

                VisualState openedState = commonGroup.States.Cast<VisualState>().FirstOrDefault(s => s.Name == Overlay.openedState);
                if (openedState != null && openedState.Storyboard != null)
                {
                    openedState.Storyboard.Completed += new EventHandler(HandleOpenStoryboardCompleted);
                }
                else
                {
                    commonGroup.CurrentStateChanged += new EventHandler<VisualStateChangedEventArgs>(HandleOpenedCommonStateChanged);
                }
            }
        }



        protected virtual void OnOpening()
        {
            page = nestedElement.GetVisualAncestors().OfType<PhoneApplicationPage>().FirstOrDefault();
            //page = (Application.Current.RootVisual as PhoneApplicationFrame).Content as PhoneApplicationPage;

            if (page != null)
            {
                page.Unloaded += new RoutedEventHandler(OnPageUnloaded);

                var eh = Opening;
                if (eh != null) eh(this, EventArgs.Empty);

                CurrentMode = Mode;
                if (CurrentMode == OverlayMode.Auto)
                {
                    CurrentMode = DetermineMode();
                }


                originalBar = page.ApplicationBar;
                OnPreparePage(page);
                EnqueueBackKey();

                if (CurrentMode == OverlayMode.Popup)
                {
                    page.LayoutUpdated += new EventHandler(HandleLayoutUpdated);
                }


                //                ReplaceApplicationBar();
                Show();
                //                if (VisualTreeHelper.GetChildrenCount(this) == 0) UpdateLayout();
                UpdateLayout();
                VisualStateManager.GoToState(this, "Unopened", false);
                Dispatcher.BeginInvoke(delegate
                {
                    VisualStateManager.GoToState(this, openedState, true);
                });

            }

        }




        private static Stack<EventHandler<CancelEventArgs>> GetBackKeyStack(DependencyObject obj)
        {
            return (Stack<EventHandler<CancelEventArgs>>)obj.GetValue(BackKeyStackProperty);
        }

        private static void SetBackKeyStack(DependencyObject obj, Stack<EventHandler<CancelEventArgs>> value)
        {
            obj.SetValue(BackKeyStackProperty, value);
        }

        private static readonly DependencyProperty BackKeyStackProperty =
            DependencyProperty.RegisterAttached("BackKeyStack", typeof(Stack<EventHandler<CancelEventArgs>>), typeof(Overlay), new PropertyMetadata(null));





        /// <summary>
        /// When the Back key is pressed, only the top most overlay should handle the event.
        /// This is ensured by using a stack to fire only for the topmost overlay:
        /// </summary>
        private void EnqueueBackKey()
        {
            var stack = GetBackKeyStack(page);
            if (stack == null)
            {
                stack = new Stack<EventHandler<CancelEventArgs>>();
                SetBackKeyStack(page, stack);
                page.BackKeyPress += new EventHandler<CancelEventArgs>(OnPageBackKeyPress);

            };
            stack.Push(HandleBackKeyPress);
        }

        private void DequeueBackKey()
        {
            var stack = GetBackKeyStack(page);
            if (stack != null)
            {
                stack.Pop();
                if (stack.Count == 0)
                {
                    page.BackKeyPress -= OnPageBackKeyPress;
                    SetBackKeyStack(page, null);
                }
            }
        }


        private void OnPageBackKeyPress(object sender, CancelEventArgs e)
        {
            var stack = GetBackKeyStack(page);
            if (stack != null)
            {
                var eh = stack.Peek();
                eh(sender, e);
            }
        }



        protected virtual void OnPageUnloaded(object sender, RoutedEventArgs e)
        {
            //if (page != null)
            //{
            //    IsOpen = false;
            //    CloseOverlay();
            //}
        }


        /// <summary>
        /// Replaces the ApplicationBar of the page withe the ApplicationBar of the Overlay.
        /// A storyboard is created to fill the Page.ApplicationBar property rather than replacing the value directly.
        /// </summary>
        /// <remarks>
        /// Using a storyboard for temporarily replacing the ApplcationBar causes sometimes an exception on Storyboard.Stop().
        /// That's why I'm still using a field to keep the original value.
        /// </remarks>
        protected virtual void ReplaceApplicationBar()
        {
            if (page != null && page.Parent != null)
            {
                var bar = ApplicationBar;
                page.ApplicationBar = bar;
            }
        }

        public virtual void RestoreApplicationBar()
        {
            if (page != null)
            {
                page.ApplicationBar = originalBar;
            }
        }

        protected virtual void OnPreparePage(PhoneApplicationPage page)
        {
        }


        private OverlayMode DetermineMode()
        {
            if (VisualTreeHelper.GetChildrenCount(page) != 1) return OverlayMode.Popup;
            Grid grid = VisualTreeHelper.GetChild(page, 0) as Grid;
            return grid != null ? OverlayMode.InPage : OverlayMode.Popup;
        }


        private void HandleBackKeyPress(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (IsOpen)
            {
                IsOpen = false;
                e.Cancel = true;
            }
        }

        /// <summary>
        /// Used when OverlayMode is Popup.
        /// </summary>
        private void HandleLayoutUpdated(object sender, EventArgs e)
        {
            if (page != null && (Width != page.ActualWidth || Height != page.ActualHeight))
            {
                Popup p = popup as Popup;
                if (p != null)
                {
                    Rect rect = ApplyRenderTransform(this, page.Orientation);
                    Width = rect.Width;
                    Height = rect.Height;
                    p.VerticalOffset = rect.Y;
                    p.HorizontalOffset = rect.X;
                }
            }
        }

        private Rect ApplyRenderTransform(FrameworkElement element, PageOrientation orientation)
        {
            GeneralTransform gt = page.TransformToVisual(Application.Current.RootVisual);
            Rect rect = gt.TransformBounds(new Rect(0, 0, page.ActualWidth, page.ActualHeight));
            double t;

            TransformGroup transforms = new TransformGroup();
            switch (orientation)
            {
                case PageOrientation.LandscapeLeft:
                    t = rect.X; rect.X = rect.Y; rect.Y = t;
                    transforms.Children.Add(new RotateTransform { Angle = 90 });
                    transforms.Children.Add(new TranslateTransform { X = page.ActualHeight });
                    break;
                case PageOrientation.LandscapeRight:
                    t = rect.X; rect.X = rect.Y; rect.Y = 0;
                    transforms.Children.Add(new RotateTransform { Angle = -90 });
                    transforms.Children.Add(new TranslateTransform { Y = page.ActualWidth });
                    break;

            }
            element.RenderTransform = transforms;
            return rect;
        }

        /// <summary>
        /// Occurs when IsOpen is set to false before the Closed transition completes.
        /// </summary>
        protected virtual void OnClosing()
        {
            //SystemTray.IsVisible = isTrayVisible;
            VisualStateManager.GoToState(this, closedState, true);

            if (page != null)
            {
                var eh = Closing;
                if (eh != null) eh(this, EventArgs.Empty);

                SetPageChildVisibility(Visibility.Visible);
                DequeueBackKey();
                page.LayoutUpdated -= HandleLayoutUpdated;
            }
        }


        private void Close()
        {
            if (CloseDelay.TotalMilliseconds > 0.0)
            {
                DispatcherTimer timer = new DispatcherTimer
                {
                    Interval = CloseDelay
                };
                timer.Tick += delegate
                {
                    timer.Stop();
                    CloseOverlay();
                };
                timer.Start();
            }
            else
            {
                CloseOverlay();
            }
        }

        private void Show()
        {
            switch (CurrentMode)
            {
                case OverlayMode.InPage: ShowInPage(); break;
                case OverlayMode.Popup: ShowPopuped(); break;
                case OverlayMode.Replace: ShowReplaced(); break;
                case OverlayMode.InFrame: ShowInFrame(); break;
                default: throw new NotSupportedException(CurrentMode.ToString());

            }
        }

        private void ShowInFrame()
        {
            var root = Application.Current.RootVisual;
            Grid grid = new Grid();
            panel = grid;
            Application.Current.RootVisual = grid;

            grid.InvokeOnLayoutUpdated(() =>
                {
                    grid.Children.Add(root);
                    grid.Children.Add(this);
                });
        }

        /// <summary>
        /// Replaces the Page with the Overlay on the PhoneApplicationFrame:
        /// </summary>
        protected virtual void ShowReplaced()
        {
            var frame = Application.Current.RootVisual as PhoneApplicationFrame;
            if (frame == null) throw new NotSupportedException("RootVisual must be PhoneApplicationFrame.");
            frame.Content = this;
        }

        /// <summary>
        /// Shows the Overlay in a popup control.
        /// </summary>
        private void ShowPopuped()
        {
            popup = new Popup
            {
                Child = this,
                IsOpen = true
            };
        }

        /// <summary>
        /// Adds the Overlay as last child of the Grid which must be the first and only element of the PhoneApplicationPage.
        /// </summary>
        private void ShowInPage()
        {
            int n = VisualTreeHelper.GetChildrenCount(page);
            Grid grid = n == 1 ? VisualTreeHelper.GetChild(page, 0) as Grid : null;

            if (grid == null)
            {
                throw new NotSupportedException("Inline mode requires the PhoneApplicationPage's first and only element to be a Grid control.");
            }

            /// span the overlay over all rows and columns (a value of 1000 rows/columns should surely be enough):
            Grid.SetColumnSpan(this, 1000);
            Grid.SetRowSpan(this, 1000);
            grid.Children.Add(this);
        }

        private void CloseOverlay()
        {
            if (page == null) return;
            RestoreApplicationBar();

            if (appBarStoryboard != null)
            {
                appBarStoryboard.Stop();
                appBarStoryboard = null;
            }

            switch (CurrentMode)
            {
                case OverlayMode.InPage: CloseInlined(); break;
                case OverlayMode.Replace: CloseReplaced(); break;
                case OverlayMode.Popup: ClosePopuped(); break;
                case OverlayMode.InFrame: CloseInFrame(); break;
                default: throw new NotSupportedException(CurrentMode.ToString());
            }

            SetPageChildVisibility(Visibility.Visible);

            originalBar = null;
            page.Unloaded -= OnPageUnloaded;
            page = null;
            OnClosed();
            ClearValue(WidthProperty);
        }



        private void CloseInFrame()
        {
#if DEBUG
            if (panel == null) throw new ArgumentNullException("panel");
#endif
            Application.Current.RootVisual = panel.Children[0];
            panel.Children.Clear();
            panel = null;
        }

        private void ClosePopuped()
        {
#if DEBUG
            if (popup == null) throw new ArgumentNullException("popup");
#endif
            popup.IsOpen = false;
            popup.Child = null;
            popup = null;
        }

        protected virtual void CloseReplaced()
        {
            var frame = Application.Current.RootVisual as PhoneApplicationFrame;
#if DEBUG
            if (frame==null) throw new ArgumentNullException("frame");  
#endif
            frame.Content = page;
        }

        private void CloseInlined()
        {
#if DEBUG
            int n = VisualTreeHelper.GetChildrenCount(page);
            Grid grid = n == 1 ? VisualTreeHelper.GetChild(page, 0) as Grid : null;
            if (grid == null) throw new ArgumentNullException("grid");
#else
            Grid grid = VisualTreeHelper.GetChild(page, 0) as Grid;
#endif
            grid.Children.Remove(this);
        }

        private void HandleOpenStoryboardCompleted(object sender, EventArgs e)
        {
            OnOpened();
        }

        private void HandleCloseStoryboardCompleted(object sender, EventArgs e)
        {
            Close();
        }

        private void HandleOpenedCommonStateChanged(object sender, VisualStateChangedEventArgs e)
        {
            if (e.NewState.Name == openedState)
            {
                OnOpened();
            }
        }

        private void HandleClosedCommonStateChanged(object sender, VisualStateChangedEventArgs e)
        {
            if (e.NewState.Name == closedState && e.OldState != null)
            {
                Close();
            }
        }

        protected virtual void SetPageChildVisibility(System.Windows.Visibility visibility)
        {

        }


        /// <summary>
        /// Occurs when IsOpen is set to true.
        /// </summary>
        protected virtual void OnOpened()
        {
            ReplaceApplicationBar();
            SetPageChildVisibility(Visibility.Collapsed);
            var eh = Opened;
            if (eh != null) eh(this, EventArgs.Empty);
        }

        /// <summary>
        /// Occurs when IsOpen was set to false after the Closed transition completes.
        /// </summary>
        protected virtual void OnClosed()
        {
            var eh = Closed;
            if (eh != null) eh(this, EventArgs.Empty);
        }


        public event EventHandler Opening;

        /// <summary>
        /// Occurs when IsOpen is set to false before the Closed transition completes.
        /// </summary>
        public event EventHandler Closing;

        /// <summary>
        /// Occurs when IsOpen was set to false after the Closed transition completes.
        /// </summary>
        public event EventHandler Closed;

        /// <summary>
        /// Occurs when IsOpen is set to true.
        /// </summary>
        public event EventHandler Opened;

    }
}
